﻿#region License
/* Copyright (C) 2009 Tim Coulter
 * 
 * This file is part of ExtremeML.
 * 
 * ExtremeML is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation either version
 * 2 of the License, or (at your option) any later version.
 * 
 * ExtremeML is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
 * the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/.
*/
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Xml;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;
using System.Reflection;
using DocumentFormat.OpenXml;

namespace ExtremeML.Builder.Survey
{
	/// <summary>
	/// Compiles and analyzes raw metadata relating to OpenXml and the SDK 2.0.
	/// </summary>
	public static class Survey
	{
		/// <summary>
		/// Runs the survey process to extract metadata from the available sources.
		/// </summary>
		public static void Run()
		{
			//FilterHelpTopicFiles();
			BuildCompositeElementReference();
		}

		/// <summary>
		/// Filters Html help topic files to include only those that pertain to classes.
		/// </summary>
		/// <remarks>
		/// The following 4 files contain invalid Xml data which requires manual correction:
		/// '3213f146-cc81-6b74-f8f2-b9f0318862b6.htm'
		/// '41680397-a77c-9003-0992-6fa8228929bc.htm'
		/// '4f17edf7-ac22-8ae4-cbc9-2592b6e1c57d.htm'
		/// 'fdac8b99-1d95-104b-1242-80e76e75be03.htm'
		/// </remarks>
		private static void FilterHelpTopicFiles()
		{
			foreach (var file in GetOriginalHelpTopicsDataDirectory().GetFiles("*.htm"))
			{
				string content;

				using (var stream = new FileStream(file.FullName, FileMode.Open))
				using (var reader = new StreamReader(stream))
				{
					content = reader.ReadToEnd();
				}

				if (content.Contains("Inheritance Hierarchy"))
				{
					file.CopyTo(Path.Combine(GetFilteredHelpTopicsDataDirectory().FullName, file.Name));
				}
			}
		}

		/// <summary>
		/// Builds a relationship reference data file for Sdk classes that derive from <see cref="OpenXmlElement"/>.
		/// </summary>
		private static void BuildCompositeElementReference()
		{
			var typeInfo = GetTypeInfoReferenceXml();
			var types = 0;
			var document = new XDocument();
			var rootElement = new XElement("classes");

			document.Add(rootElement);

			// First pass extracts the type information and its schema markup from each topic file
			foreach (var file in GetFilteredHelpTopicsDataDirectory().GetFiles("*.htm"))
			{
				var parsed = ParsedTopic.Create(file);

				if (parsed != null)
				{
					var parsedInfo = typeInfo.XPathSelectElement(string.Format(
						"//doc/namespace[@name='{0}']/class[@name='{1}']",
						parsed.Type.Namespace,
						parsed.Type.Name));

					if (parsedInfo == null)
					{
						Debug.WriteLine(string.Format("TypeInfo metadata not available for type {0}", parsed.Type.FullName));
						continue;
					}

					var parentElement = new XElement("class",
						new XAttribute("name", parsed.Type.FullName),
						new XAttribute("tag", parsedInfo.Attribute("tag").Value),
						new XAttribute("type", parsedInfo.Attribute("type").Value)
					);

					var parsedProperties = GetReflectedChildElementProperties(parsed.Type);
					var single = new XElement("singleInstanceChildElements");
					var multiple = new XElement("multipleInstanceChildElements");

					foreach (var child in parsedInfo.XPathSelectElements(".//children/child"))
					{
						var childInfo = typeInfo.XPathSelectElement(string.Format(
							"//class[@id='{0}']",
							child.Attribute("id").Value));

						var childNamespace = childInfo.XPathSelectElement("ancestor::namespace").Attribute("name").Value;
						var childFullName = childNamespace + "." + childInfo.Attribute("name").Value;
						var childTag = childInfo.Attribute("tag").Value;

						int? minOccurs = default(int?);
						int? maxOccurs = default(int?);
						string property = null;

						if (parsed.SchemaChildren != null && parsed.SchemaChildren.ContainsKey(childTag))
						{
							minOccurs = parsed.SchemaChildren[childTag].MinOccurs;
							maxOccurs = parsed.SchemaChildren[childTag].MaxOccurs;
						}

						if (parsedProperties.ContainsKey(childFullName))
						{
							maxOccurs = 1;
							property = parsedProperties[childFullName];
						}

						if (maxOccurs.HasValue)
						{
							var childElement = new XElement("child",
								new XAttribute("name", childFullName),
								new XAttribute("tag", childTag),
								new XAttribute("type", childInfo.Attribute("type").Value),
								new XAttribute("maxOccurs", maxOccurs)
							);

							childElement.SetAttributeValue("minOccurs", minOccurs);
							childElement.SetAttributeValue("property", property);

							if (maxOccurs.Value > 1 || maxOccurs.Value == -1)
							{
								multiple.Add(childElement);
							}
							else
							{
								single.Add(childElement);
							}
						}
						else
						{
							Debug.WriteLine(string.Format("No constraint data for child '{0}' of type {1}", childTag, parsed.Type.FullName));
							continue;
						}
					}

					parentElement.Add(single);
					parentElement.Add(multiple);
					rootElement.Add(parentElement);
					types++;
				}
			}

			Debug.WriteLine(string.Format("{0} OpenXmlElement types extracted.", types));
			document.Save(GetElementReferenceOutputPath());
		}


		/// <summary>
		/// Returns a dictionary that resolves property names from their types
		/// for the element properties of the passed <see cref="Type"/>
		/// </summary>
		private static Dictionary<string, string> GetReflectedChildElementProperties(
			Type type)
		{
			var result = new Dictionary<string, string>();

			foreach (var property in type.GetProperties().Where(item => Utility.IsElementType(item.PropertyType)))
			{
				result.Add(property.PropertyType.FullName, property.Name);
			}

			return result;
		}

		/// <summary>
		/// Returns the OpenXml TypeInfo reference as an <see cref="XDocument"/>.
		/// </summary>
		private static XDocument GetTypeInfoReferenceXml()
		{
			using (var stream = new FileStream(GetTypeInfoReferencePath(), FileMode.Open))
			using (var reader = XmlReader.Create(stream))
			{
				return XDocument.Load(reader);
			}
		}

		#region Path Lookup

		/// <summary>
		/// Returns the path to the OpenXml TypeInfo reference file (extracted from Classes Explorer).
		/// </summary>
		private static string GetTypeInfoReferencePath()
		{
			return Path.Combine(GetProjectDataDirectory().FullName, "TypeInfo.xml");
		}

		/// <summary>
		/// Returns the path to the OpenXmlElement reference output file.
		/// </summary>
		private static string GetElementReferenceOutputPath()
		{
			return Path.Combine(GetProjectDataDirectory().FullName, "ChildElementReference.xml");
		}

		/// <summary>
		/// Returns a <see cref="DirectoryInfo"/> representing the folder where original help topics are stored.
		/// </summary>
		private static DirectoryInfo GetOriginalHelpTopicsDataDirectory()
		{
			return GetProjectDataDirectory().GetDirectories("Original Help Topics")[0];
		}

		/// <summary>
		/// Returns a <see cref="DirectoryInfo"/> representing the folder where class reference help topics are filtered.
		/// </summary>
		private static DirectoryInfo GetFilteredHelpTopicsDataDirectory()
		{
			return GetProjectDataDirectory().GetDirectories("Help Topics")[0];
		}

		/// <summary>
		/// Returns a <see cref="DirectoryInfo"/> representing the data folder of this project.
		/// </summary>
		private static DirectoryInfo GetProjectDataDirectory()
		{
			return GetProjectRootDirectory().GetDirectories("Data")[0];
		}

		/// <summary>
		/// Returns a <see cref="DirectoryInfo"/> representing the root folder of this project.
		/// </summary>
		private static DirectoryInfo GetProjectRootDirectory()
		{
			return GetSolutionRootDirectory().GetDirectories("Builder")[0];
		}

		/// <summary>
		/// Returns a <see cref="DirectoryInfo"/> representing the root folder of this solution.
		/// </summary>
		private static DirectoryInfo GetSolutionRootDirectory()
		{
			return new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent.Parent.Parent;
		}

		#endregion
	}
}
